#include <opengl/window.h>
#include <opengl/application.h>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <string>
#include <iostream>
#include <functional>

namespace opengl
{

static Application* application = Application::instance();

//! @brief 初始化 glfw
void Window::init()
{
    // 初始化 glfw
    if (glfwInit() == GLFW_FALSE)
    {
        std::cout << "failed to init glfw\n";
        exit(-1);
    }

    // 配置 OpenGL
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
}

//! @brief 注销 glfw
void Window::terminate()
{
    glfwTerminate();
}

Window::Window(int width, int height, std::string title, const glm::vec4& backColor) noexcept :
    _width(width), _height(height), 
    _title(std::move(title)), _backColor(backColor),
    _window(nullptr)
{
    try 
    {
#ifdef DEBUG
        std::cout << "init OpenGL\n";
#endif
        this->initWindow();
    }
    catch(std::string message)
    {
        std::cout << message;
        exit(-1);
    }

    // 开启 OpenGL 功能
#ifdef DEBUG
    std::cout << "enable OpenGL function\n";
#endif
    this->enbaleFunction();

    application->windows().push_back(this);
}

Window::~Window() noexcept
{
}

//! @brief 初始化 OpenGL
void Window::initWindow()
{
    // 创建窗口对象
    this->_window = glfwCreateWindow(this->_width, this->_height, this->_title.c_str(), NULL, NULL);
    if (this->_window == NULL)
        throw std::string("Failed to create GLFW window\n");

    // 选择新建的窗口到上下文
    this->bind();
    // 开启鼠标捕捉
    glfwSetInputMode(this->_window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    // 注册回调函数
    this->registerCallBack();

    // 初始化 GLAD
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
        throw std::string("Failed to initialize GLAD\n");
}

//! @brief 开启 OpenGL 功能
void Window::enbaleFunction() noexcept
{
    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}

//! @brief 注册回调函数
void Window::registerCallBack() noexcept
{    
    glfwSetFramebufferSizeCallback(this->_window, Window::framebufferSizeCallbackInterface);
    glfwSetCursorPosCallback(this->_window, Window::cursorPosCallbackInterface);
    glfwSetScrollCallback(this->_window, Window::scrollCallbackInterface);
    glfwSetMouseButtonCallback(this->_window, Window::mouseButtonCallbackInterface);
    glfwSetKeyCallback(this->_window, Window::keyboardCallbackInterface);
}

//! @brief 绑定当前窗口到上下文
void Window::bind()
{
    glfwMakeContextCurrent(this->_window);
    application->switchWindow(this);
}

//! @brief 渲染循环
void Window::show()
{
    while(!glfwWindowShouldClose(this->_window))
    {
        glClearColor(this->_backColor.x, this->_backColor.y, this->_backColor.z, this->_backColor.w);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 

        this->processInput();

        this->render();

        glfwSwapBuffers(this->_window);
        glfwPollEvents();    
    }
}

//! @brief 释放资源
void Window::free()
{
    // 释放子类资源
    this->freeResources();

    glfwDestroyWindow(this->_window);
}

//! @brief 更新视口大小回调函数
void Window::framebufferSizeCallback(GLFWwindow* window, int width, int height) {}

//! @brief 鼠标移动回调
void Window::cursorPosCallback(GLFWwindow* window, double xpos, double ypos) {}

//! @brief 鼠标滚轮回调
void Window::scrollCallback(GLFWwindow* window, double xoffset, double yoffset) {}

//! @brief 鼠标点击回调
void Window::mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) {}

//! @brief 键盘输入回调
void Window::keyboardCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {}

void Window::framebufferSizeCallbackInterface(GLFWwindow* window, int width, int height)
{
    application->currentWindow()->framebufferSizeCallback(window, width, height);
}

void Window::cursorPosCallbackInterface(GLFWwindow* window, double xpos, double ypos)
{
    application->currentWindow()->cursorPosCallback(window, xpos, ypos);
}

void Window::scrollCallbackInterface(GLFWwindow* window, double xoffset, double yoffset)
{
    application->currentWindow()->scrollCallback(window, xoffset, yoffset);
}

void Window::mouseButtonCallbackInterface(GLFWwindow *window, int button, int action, int mods)
{
    application->currentWindow()->mouseButtonCallback(window, button, action, mods);
}

void Window::keyboardCallbackInterface(GLFWwindow* window, int key, int scancode, int action, int mods)
{
    application->currentWindow()->keyboardCallback(window, key, scancode, action, mods);
}

}
